package common.extensions;

import static org.junit.Assert.*;

import java.util.*;

import org.junit.*;

@SuppressWarnings({"javadoc", "static-method"})
public class ExtenderTests {
	
	private static final int	SET_SIZE	= 100;
	private Set<Integer>		set;
	private List<Integer>		list;
	
	private static class Pair {
		public final int	first;
		public final int	second;
		
		public Pair(int first, int second) {
			this.first = first;
			this.second = second;
		}
		
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = (prime * result) + first;
			result = (prime * result) + second;
			return result;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			Pair other = (Pair)obj;
			if (first != other.first) {
				return false;
			}
			if (second != other.second) {
				return false;
			}
			return true;
		}
		
	}
	
	private static boolean isPrime(int x) {
		if (x == 1) {
			return false;
		}
		if (x == 2) {
			return true;
		}
		if ((x % 2) == 0) {
			return false;
		}
		for (int i = 3; i < Math.sqrt(x); i += 2) {
			if ((x % i) == 0) {
				return false;
			}
		}
		
		return true;
	}
	
	@Before
	public void setUp() throws Exception {
		list = new ArrayList<>(SET_SIZE);
		for (int i = 1; i <= SET_SIZE; i++) {
			list.add(i);
		}
		set = new HashSet<>(list);
	}
	
	@Test
	public void testCountIterableOfT() {
		assertEquals(SET_SIZE, Extender.count(set));
	}
	
	@Test
	public void testCountIterableOfQextendsTPredicateOfT() {
		assertEquals(SET_SIZE / 10, Extender.count(set, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return (element % 10) == 0;
			}
		}));
	}
	
	@Test
	public void testWhere() {
		Set<Integer> newSet = new HashSet<>();
		for (int i = 10; i < 20; i += 2) {
			newSet.add(i);
		}
		assertEquals(newSet, new HashSet<>(Extender.where(set, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return (element > 9) && (element < 20) && ((element % 2) == 0);
			}
		})));
	}
	
	@Test
	public void testFirstIterableOfT() {
		assertEquals(1, (int)Extender.first(list));
	}
	
	@Test
	public void testFirstIterableOfTPredicateOfT() {
		assertEquals(41, (int)Extender.first(list, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return element > 40;
			}
			
		}));
	}
	
	@Test
	public void testLastIterableOfT() {
		assertEquals(100, (int)Extender.last(list));
	}
	
	@Test
	public void testLastIterableOfTPredicateOfT() {
		assertEquals(97, (int)Extender.last(list, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return isPrime(element);
			}
		}));
	}
	
	@Test
	public void testAny() {
		assertTrue(Extender.any(set, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return isPrime(element);
			}
		}));
		assertFalse(Extender.any(set, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return (element < 1) || (element > 100);
			}
		}));
	}
	
	@Test
	public void testAll() {
		assertFalse(Extender.all(set, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return isPrime(element);
			}
		}));
		assertTrue(Extender.all(set, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return (element > 0) && (element <= 100);
			}
		}));
	}
	
	@Test
	public void testIsEmpty() {
		assertFalse(Extender.isEmpty(set));
		assertTrue(Extender.isEmpty(new HashSet<>()));
		
	}
	
	@Test
	public void testToDictionary() {
		Map<Integer, Pair> dict = Extender.toDictionary(Extender.map(set, new Function<Integer, Pair>() {
			@Override
			public Pair calculate(Integer element) {
				return new Pair(element, element * element);
			}
		}), new Function<Pair, Integer>() {
			@Override
			public Integer calculate(Pair element) {
				return element.first;
			}
		});
		for (int i: list) {
			assertEquals(i * i, dict.get(i).second);
		}
	}
	
	@Test
	public void testMap() {
		List<Integer> newList = new LinkedList<>();
		for (int i = 1; i <= SET_SIZE; i++) {
			newList.add(i * i);
		}
		assertEquals(newList, Extender.map(list, new Function<Integer, Integer>() {
			@Override
			public Integer calculate(Integer element) {
				return element * element;
			}
			
		}));
	}
	
	@Test
	public void testRangeInt() {
		List<Integer> newList = new LinkedList<>();
		for (int i: Extender.range(SET_SIZE)) {
			newList.add(i + 1);
		}
		assertEquals(list, newList);
	}
	
	@Test
	public void testRangeIntInt() {
		List<Integer> newList = new LinkedList<>();
		for (int i: Extender.range(10, SET_SIZE - 10)) {
			newList.add(i + 1);
		}
		assertEquals(Extender.take(Extender.skip(list, 10), 80), newList);
	}
	
	@Test
	public void testRangeIntIntInt() {
		List<Integer> newList = new LinkedList<>();
		for (int i: Extender.range(10, SET_SIZE - 10, 2)) {
			newList.add(i);
		}
		assertEquals(Extender.take(Extender.skip(Extender.where(list, new Predicate<Integer>() {
			@Override
			public Boolean calculate(Integer element) {
				return (element % 2) == 0;
			}
		}), 4), 40), newList);
	}
	
	@Test
	public void testMax() {
		assertEquals(100, Extender.max(set).intValue());
	}
	
	@Test
	public void testMin() {
		assertEquals(1, Extender.min(set).intValue());
	}
	
	@Test
	public void testTake() {
		assertEquals(90, Extender.take(set, 90).size());
	}
	
	@Test
	public void testSkip() {
		assertEquals(90, Extender.skip(set, 10).size());
	}
	
	@Test
	public void testSum() {
		assertEquals(5050, Extender.sum(set).intValue());
	}
	
	@Test
	public void testProduct() {
		assertEquals(720, Extender.product((Extender.take(set, 6))).intValue());
	}
	
	//
	// @Test
	// public void testAggregate() {
	// fail("Not yet implemented");
	// }
	//
	// @Test
	// public void testReverseTArray() {
	// fail("Not yet implemented");
	// }
	//
	// @Test
	// public void testReverseIterableOfT() {
	// fail("Not yet implemented");
	// }
	//
	// @Test
	// public void testGroupBy() {
	// fail("Not yet implemented");
	// }
	//
	// @Test
	// public void testOrderBy() {
	// fail("Not yet implemented");
	// }
	@Test
	public void testAverage() {
		assertEquals(50.5, Extender.average(set), 0);
	}
	
	@Test
	public void testContains() {
		assertTrue(Extender.contains(set, 50));
		assertFalse(Extender.contains(set, 0));
	}
	
	@Test
	public void testDelimit() throws Exception {
		Iterable<String> iterable = Arrays.asList("foo", "bar");
		assertEquals("foo||bar", Extender.delimit(iterable, "||"));
	}
}
